# gozerbot/commands.py
#
#

""" 

    This module implements the commands a user can give. It also contains 
    the global cmnds object to which all commands are added.

    example:

    ::

        cmnds.add('hello', handle_hello, 'USER')


    :var cmnds: global commands object

"""

__copyright__ = 'this file is in the public domain'

# IMPORT SECTION

# gozerbot imports
from gozerbot.stats import stats
from utils.generic import makeoptions
from eventbase import defaultevent
from config import config
from utils.log import rlog
from utils.trace import calledfrom
from utils.exception import handle_exception
from utils.locking import lockdec

# basic imports
import sys, re, copy, types

# END IMPORT

# LOCK SECTION

# command lock

class Command(object):

    """
        implements a command.

        :param func: the function to call when a commands gets triggered
        :param perm: permissions the command needs before it gets fired
        :type perm: list
        :param plugname: the plugin this commands is implemented in
        :type param: string
        :param threaded: determines if the command has to be run in its own thread
        :type threaded: False or True
        :param allowqueue: determines if output of this commands can be used in pipeline queues
        :type threaded: False or True
        :param options: options for this command
        :type options: dict

    """

    def __init__(self, func, perm, plugname, speed=5, threaded=False, allowqueue=True, options={'--speed': 5, '--chan': '', '--filter': '', '--how': 'msg'}):

        self.name = str(func) # function name is string representation of the function 
        self.func = func # function to call

        # make sure permission(s) are stored in a list
        if type(perm) == types.ListType:
            self.perms = list(perm)
        else:
            self.perms = [perm, ]

        self.plugname = plugname # plugin name
        self.speed = copy.deepcopy(speed) # speed to execute command with
        self.threaded = copy.deepcopy(threaded) # set if threaded exec is required
        self.allowqueue = copy.deepcopy(allowqueue) # set if command is allowed to be used in pipeline
        self.options = dict(options) # options set in the command 
        self.activate = False
        
class Commands(dict):

    """ 
        the commands object is a dict containing the commands. dict key is the 
        command (1 word).

    """

    def __setitem__(self, name, value):

        """ set command. """

        dict.__setitem__(self, name, value)

    def __delitem__(self, name):

        """ delete command. """

        dict.__delitem__(self, name)

    def size(self):

        """
            nr of commands.

            :rtype: integer

            .. literalinclude:: ../../gozerbot/commands.py
                :pyobject: Commands.size

        """

        return len(self)

    def activate(self, plugname):
        cp = dict(self)
        for i in cp.values():
            if i.plugname == plugname:
                i.activate = True

    def disable(self, plugname):
        cp = dict(self)
        for i in cp.values():
            if i.plugname == plugname:
                i.activate = False

    def whatperms(self):

        """
            return all possible permissions.

            :rtype: list

            .. literalinclude:: ../../gozerbot/commands.py
                :pyobject: Commands.whatperms

        """

        result = []

        # loop over the commands and collect all possible permissions
        cp = dict(self)
        for i in cp.values():
            for j in i.perms:
                if j not in result:
                    result.append(j)

        return result

    def list(self, perm):

        """
            list commands with permission perm.

            :param perm: the permission
            :type perm: string
            :rtype: list

            .. literalinclude:: ../../gozerbot/commands.py
                :pyobject: Commands.list

        """

        result = []

        # make sure perm is a list
        if type(perm) != types.ListType:
            perm = perm.upper()
            perms = [perm, ]
        else:
            perms = perm

        # loop over commands collecting all command having permission
        cp = dict(self)
        for name, cmnd in cp.iteritems():
            for i in perms:
                if i in cmnd.perms and name not in result:
                    result.append(name)

        return result

    def getfuncnames(self, plug):

        """
            get all function names of commands in a plugin.

            :param plug: plugname
            :type plug: string
            :rtype: list

            .. literalinclude:: ../../gozerbot/commands.py
                :pyobject: Commands.getfuncnames

        """

        result = []

        # collect function names 
        cp = dict(self)
        for i in cp.values():
            if i.plugname == plug:
                result.append(i.func.func_name)

        return result

    def getoptions(self, command):

        """
            get options of a command.

            :param command: the command to get options for
            :type command: string
            :rtype: dict

            .. literalinclude:: ../../gozerbot/commands.py
                :pyobject: Commands.getoptions

        """

        cp = dict(self)
        for name, cmnd in cp.iteritems():
            if name == command:
                return makeoptions(defaultevent, cmnd.options)

    def permoverload(self, name, perms):

        """
             overload permission of function with funcname.

            :param name: name of command to overload permission of
            :type name: string
            :param perms: permission to overload the command with
            :type perms: list
            :rtype: boolean

            .. literalinclude:: ../../gozerbot/commands.py
                :pyobject: Commands.permoverload

        """

        # make sure all perms are uppercase
        perms = [perm.upper() for perm in perms]

        # overload the command with given permissions
        for com in self.values():
            try:
                if com.func.func_name == name:
                    com.perms = perms
                    return True
            except AttributeError:
                rlog(10, 'commands', "permoverload: no %s function" % name)
        return False

    def add(self, cmnd, func, perm, speed=5, threaded=False, allowqueue=True, options={'--speed': 5, '--chan': '', '--filter': '', '--how': 'msg'}):
	
        """
            add a command.

            :param func: the function to call when a commands gets triggered
            :param perm: permissions the command needs before it gets fired
            :type perm: list
            :param plugname: the plugin this commands is implemented in
            :type param: string
            :param threaded: determines if the command has to be run in its own thread
            :type threaded: boolean
            :param allowqueue: determines if output of this commands can be used in pipeline queues
            :type threaded: boolean
            :param options: options for this command
            :type options: dict

            .. literalinclude:: ../../gozerbot/commands.py
                :pyobject: Commands.add

        """

        plugname = calledfrom(sys._getframe())

        # check if plugin is in loadlist .. if not dont register command. 
        if config['loadlist'] and plugname not in config['loadlist']:
            rlog(10, 'commands', 'NOT LOADING %s' % plugname)
            return

        rlog(0, 'commands', 'added %s (%s) ' % (cmnd, plugname))

        # add command
        self[cmnd.lower()] = Command(func, perm, plugname, speed, threaded, allowqueue, options)
        self[cmnd.lower()].name = cmnd.lower()

    def apropos(self, txt, perms=[]):

        """
            search for command.

            :param txt: txt to search commands with
            :type txt: string
            :param perms: contain permission that must match first
            :type perms: list
            :rtype: list

            .. literalinclude:: ../../gozerbot/commands.py
                :pyobject: Commands.apropos

        """

        result = []

        #  loop over commands collecting all commands that contain given txt
        cp = dict(self)
        for name, cmnd in cp.iteritems():
            if perms:
                go = False
                for i in perms:
                    if i in cmnd.perms:
                        go = True
                if not go:
                    continue                
            if re.search(txt, name):
                result.append(name)

        return result

    def unload(self, plugname):

        """
            unload plugin commands.

            :param plugname: name of the plugin that needs to be unloaded
            :type plugname: string
            :rtype: boolean

            .. literalinclude:: ../../gozerbot/commands.py
                :pyobject: Commands.unload

        """

        results = []

        # look for the commands registerd in plugin
        for name, cmnd in self.iteritems():
            if cmnd.plugname == plugname:
                results.append(name)

        got = False

        # remove commands
        for name in results:

            try:
                del self[name]
                rlog(-3, 'commands', 'unloaded %s (%s)' % (name, plugname))
                got = True
            except KeyError:
                got = False

        if got:
            return True

    def whereis(self, command):

        """
            locate plugin a command is registered in.

            :param command: command to search for
            :type command: string
            :rtype: list

            .. literalinclude:: ../../gozerbot/commands.py
                :pyobject: Commands.whereis

        """

        result = []

        # find plugin 
        cp = dict(self)
        for name, cmnd in cp.iteritems():
            if name == command:
                if not cmnd.plugname in result:
                    result.append(cmnd.plugname)

        return result

    def perms(self, name):

        """
            get permission of command.

            :param command: command to lookup permissions for
            :type command: string
            :rtype: list

            .. literalinclude:: ../../gozerbot/commands.py
                :pyobject: Commands.perms

        """

        name = name.lower()

        if self.has_key(name):
            return self[name].perms
        else:
            return []

    def setperm(self, command, perm):

        """
            set permission of command.

            :param command: command to set permission of
            :type command: string
            :param perm: the permission
            :type perm: string
            :rtype: boolean

            .. literalinclude:: ../../gozerbot/commands.py
                :pyobject: Commands.setperm

        """

        command = command.lower()
        perm = perm.upper()

        if self.has_key(command):
            if perm not in self[command].perms:
                self[command].perms.append(perm)
            return True
        return False

    def getcommand(self, txt):

        """
            return command matching txt.

            :param txt: txt to match commands against
            :type txt: string

            .. literalinclude:: ../../gozerbot/commands.py
                :pyobject: Commands.getcommand

        """

        textlist = txt.split()

        if not textlist:
            return None

        cmnd = textlist[0].lower()
        if self.has_key(cmnd):
            com = self[cmnd] # the command
            return com
        else:
            return None

    def options(self, command):

        """
            return options dict of command.

            :param command: the command to get the options of
            :type command: string

            .. literalinclude:: ../../gozerbot/commands.py
                :pyobject: Commands.options

        """

        try:
            return self[command].options
        except KeyError:
            return 

    def getcommands(self, plugin):

        """
            get all commands of a plugin.

            :param plugin: the plugin to get commands of
            :type plugin: string

            .. literalinclude:: ../../gozerbot/commands.py
                :pyobject: Commands.getcommands

        """

        result = []
        
        tmp = dict(self)
        for name, cmnd in tmp.iteritems():
            if cmnd.plugname == plugin:
                result.append(name)

        return result

    def dispatch(self, com, txt, wait=False):

        """
            dispatch command.

            :param com: the command object to dispatch with
            :type com: Command

            .. literalinclude:: ../../gozerbot/commands.py
                :pyobject: Commands.dispatch

        """

        com.func(txt)
        return 1

class Botcommands(Commands):

    """
        commands for the bot .. dispatch with (bot, ircevent) as arguments. 

    """

    def dispatch(self, com, bot, ievent, wait=False):

        """
             dispatch on event passing bot and ievent as arguments.

             :param com: the command object
             :type com: gozerbot.commands.Command
             :param bot: the bot this command is given on
             :type bot: gozerbot.botbase.BotBase
             :param ievent: the event triggering this command
             :rtype: boolean

             .. literalinclude:: ../../gozerbot/commands.py
                 :pyobject: Botcommands.dispatch

        """

        if not com.activate:
            return False

        if bot.stopped:
            return False

        #ievent = copy.deepcopy(ieventin)

        # stats
        stats.up('cmnds', com.name)
        stats.up('cmnds', com.plugname)
        stats.up('cmnds', 'speed%s' % com.speed)

        # execute command
        com.func(bot, ievent)
        return True

# INIT SECTION

cmnds = Botcommands()

# END INIT
